﻿using System;
using System.IO;
using System.Security.Cryptography;
using System.Text;
using HK.Core.Common.Data;
using HLogger = HK.Core.Logs.Logger;

namespace HK.Core.Utils
{
    /// <summary>
    /// 校验模式
    /// </summary>
    public enum CheckMode
    {
        Md5
    }

    /// <summary>
    /// 位置校验模式异常
    /// </summary>
    public class UnknownCheckModeException : NotImplementedException
    {
        public UnknownCheckModeException(CheckMode iMode) : base($"Unknown CheckMode!(Mode:{iMode})") { }
    }
    
    /// <summary>
    /// 位置校验模式异常
    /// </summary>
    public class UnknownCheckModeException<TData> : NotImplementedException
        where TData : JsonData
    {
        public UnknownCheckModeException(CheckMode iMode) : base($"Unknown CheckMode!(Type:{typeof(TData).Name} Mode:{iMode})") { }
    }
    
    /// <summary>
    /// 工具类 : 校验码.
    /// </summary>
    public static class UtilsCheckCode
    {

        /// <summary>
        /// 加密 - MD5
        /// </summary>
        /// <param name="iFilePath">文件路径</param>
        /// <param name="iMode">校验模式(默认:Md5)</param>
        /// <param name="iLower">小写标志位</param>
        /// <returns>MD5码</returns>
        public static string Encode(string iFilePath, CheckMode iMode = CheckMode.Md5, bool iLower = true)
        {
            if (!File.Exists(iFilePath))
            {
                HLogger.Error($"UtilsMd5::Encode():File is not exist!(Path:{iFilePath})");
                return null;
            }

            string md5 = null;
#if UNITY_EDITOR
            try
            {
#endif
                switch (iMode)
                {
                    case CheckMode.Md5:
                        md5 = EncodeMd5(iFilePath, iLower);
                        break;
                    default:
                        throw new UnknownCheckModeException(iMode);
                }
                
#if UNITY_EDITOR
            }
            catch (Exception iExp)
            {
                HLogger.Fatal($"UtilsCheckCode::Encode():" +
                              $"\n  File:{iFilePath}" +
                              $"\n  Exception:{iExp.GetType().Name} Exception:{iExp.GetType().Name}" +
                              $"\n  Error:{iExp.Message} Exception:{iExp.GetType().Name}" +
                              $"\n  StackTrace:{iExp.StackTrace}");
            }
#endif
            return md5;
        }

        /// <summary>
        /// 加密 - MD5
        /// </summary>
        /// <param name="iData">数据</param>
        /// <param name="iMode">校验模式(默认:Md5)</param>
        /// <param name="iLower">小写标志位</param>
        /// <returns>MD5码</returns>
        /// <typeparam name="TData">数据类型</typeparam>
        public static string Encode<TData>(TData iData, CheckMode iMode = CheckMode.Md5, bool iLower = true)
            where TData : JsonData
        {
            return Encode<TData>(iData, Encoding.UTF8, iMode, iLower);
        }

        /// <summary>
        /// 加密 - MD5
        /// </summary>
        /// <param name="iData">数据</param>
        /// <param name="iEncoding">字节编码</param>
        /// <param name="iMode">校验模式(默认:Md5)</param>
        /// <param name="iLower">小写标志位</param>
        /// <returns>MD5码</returns>
        /// <typeparam name="TData">数据类型</typeparam>
        public static string Encode<TData>(TData iData, Encoding iEncoding, CheckMode iMode = CheckMode.Md5, bool iLower = true)
            where TData : JsonData
        {
            string md5 = null;
#if UNITY_EDITOR
            try
            {
#endif
                switch (iMode)
                {
                    case CheckMode.Md5:
                        md5 = EncodeMd5<TData>(iData, iEncoding, iLower);
                        break;
                    default:
                        throw new UnknownCheckModeException<TData>(iMode);
                }
                
#if UNITY_EDITOR
            }
            catch (Exception iExp)
            {
                HLogger.Fatal($"UtilsCheckCode::Encode():Type:{typeof(TData).Name}" +
                              $"\n  Encoding:{iEncoding.EncodingName}" +
                              $"\n  Exception:{iExp.GetType().Name} Exception:{iExp.GetType().Name}" +
                              $"\n  Error:{iExp.Message} Exception:{iExp.GetType().Name}" +
                              $"\n  StackTrace:{iExp.StackTrace}");
            }
#endif
            return md5;
        }

        /// <summary>
        /// MD5加密
        /// </summary>
        /// <param name="iFilePath">文件路径</param>
        /// <param name="iMode">校验模式(默认:Md5)</param>
        /// <param name="iLower">小写标志位</param>
        /// <returns>MD5码</returns>
        public static string EncodeTextFile(string iFilePath, CheckMode iMode = CheckMode.Md5, bool iLower = true)
        {
            return EncodeTextFile(iFilePath, Encoding.UTF8, iMode, iLower);
        }

        /// <summary>
        /// MD5加密
        /// </summary>
        /// <param name="iFilePath">文件路径</param>
        /// <param name="iEncoding">字节编码</param>
        /// <param name="iMode">校验模式(默认:Md5)</param>
        /// <param name="iLower">小写标志位</param>
        /// <returns>MD5码</returns>
        public static string EncodeTextFile(string iFilePath, Encoding iEncoding, CheckMode iMode = CheckMode.Md5, bool iLower = true)
        {
            if (!File.Exists(iFilePath))
            {
                HLogger.Error($"UtilsMd5::EncodeTextFile():File is not exist!(Path:{iFilePath})");
                return null;
            }
            
            string md5 = null;
#if UNITY_EDITOR
            try
            {
#endif
                switch (iMode)
                {
                    case CheckMode.Md5:
                        md5 = EncodeTextFileMd5(iFilePath, iEncoding, iLower);
                        break;
                    default:
                        throw new UnknownCheckModeException(iMode);
                }
                
#if UNITY_EDITOR
            }
            catch (Exception iExp)
            {
                HLogger.Fatal($"UtilsCheckCode::Encode():" +
                              $"\n  File:{iFilePath}" +
                              $"\n  Encoding:{iEncoding.EncodingName}" +
                              $"\n  Exception:{iExp.GetType().Name} Exception:{iExp.GetType().Name}" +
                              $"\n  Error:{iExp.Message} Exception:{iExp.GetType().Name}" +
                              $"\n  StackTrace:{iExp.StackTrace}");
            }
#endif
            return md5;
        }

#region Md5
        
        /// <summary>
        /// 加密 - MD5
        /// </summary>
        /// <param name="iFilePath">文件路径</param>
        /// <param name="iLower">小写标志位</param>
        /// <returns>MD5码</returns>
        private static string EncodeMd5(string iFilePath, bool iLower = true)
        {
            string md5 = null;
            // 读取文件
            using (var fs = new FileStream(iFilePath, FileMode.Open, FileAccess.Read, FileShare.Read))
            {
                var length = (int)fs.Length;
                var bytes = new byte[length];
                var read = fs.Read(bytes, 0, length);

                md5 = EncodeMd5(bytes, iLower);
            }

            return md5;
        }

        /// <summary>
        /// 加密 - MD5
        /// </summary>
        /// <param name="iData">数据</param>
        /// <param name="iEncoding">字节编码</param>
        /// <param name="iLower">小写标志位</param>
        /// <returns>MD5码</returns>
        /// <typeparam name="TData">数据类型</typeparam>
        private static string EncodeMd5<TData>(TData iData, Encoding iEncoding, bool iLower = true)
            where TData : JsonData
        {
            if (default(TData) == iData || !iData.Valid)
            {
                HLogger.Error($"UtilsMd5::Encode():The json data is null or invalid!(Type:{typeof(TData).Name})");
                return null;
            }

            var jsonContext = UtilsJson<TData>.ConvertToJsonString(iData, false);
            if (string.IsNullOrEmpty(jsonContext))
            {
                HLogger.Error($"UtilsMd5::Encode():The json data is null or empty!(Type:{typeof(TData).Name})");
                return null;
            }

            var bytes = iEncoding.GetBytes(jsonContext);
            if (0 >= bytes.Length)
            {
                HLogger.Error($"UtilsMd5::EncodeTextFile():There is no data exist in this json data!(Type:{typeof(TData).Name} Encoding:{iEncoding})");
                return null;
            }
            
            return EncodeMd5(bytes, iLower);

        }

        /// <summary>
        /// MD5加密
        /// </summary>
        /// <param name="iFilePath">文件路径</param>
        /// <param name="iEncoding">字节编码</param>
        /// <param name="iLower">小写标志位</param>
        /// <returns>MD5码</returns>
        private static string EncodeTextFileMd5(string iFilePath, Encoding iEncoding, bool iLower = true)
        {
            var context = File.ReadAllText(iFilePath, iEncoding);
            if (string.IsNullOrEmpty(context))
            {
                HLogger.Error($"UtilsMd5::EncodeTextFileMd5():The context of file is empty!(Path:{iFilePath})");
                return null;
            }
            
            var bytes = iEncoding.GetBytes(context);
            if (0 < bytes.Length)
            {
                HLogger.Error($"UtilsMd5::EncodeTextFileMd5():There is no data exist in this file!(Path:{iFilePath} Encoding:{iEncoding})");
                return null;
            }
            return EncodeMd5(bytes, iLower);

        }
        
        /// <summary>
        /// 加密 - MD5
        /// </summary>
        /// <param name="iBytes">加密字节列表</param>
        /// <param name="iLower">小写标志位</param>
        /// <returns>MD5码</returns>
        private static string EncodeMd5(byte[] iBytes, bool iLower = true)
        {
            if (null == iBytes || 0 >= iBytes.Length) return null;
            
            string md5 = null;
            using (var md5Provider = new MD5CryptoServiceProvider())
            {
                var buffer = new StringBuilder();
                var md5Bytes = md5Provider.ComputeHash(iBytes);
                if (0 < md5Bytes.Length)
                {
                    var count = md5Bytes.Length;
                    for (var idx = 0; idx < count; ++idx)
                    {
                        var charByte = md5Bytes[idx];
                        buffer.Append(charByte.ToString("X"));
                    }
                    md5 = iLower ? buffer.ToString().ToLower() : buffer.ToString();
                }
            }

            return md5;
        }
        
#endregion
        
    }
}